﻿#region Copyright

// // The contents of this file are subject to the Mozilla Public License
// // Version 1.1 (the "License"); you may not use this file except in compliance
// // with the License. You may obtain a copy of the License at
// //   
// // http://www.mozilla.org/MPL/
// //   
// // Software distributed under the License is distributed on an "AS IS"
// // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// // License for the specific language governing rights and limitations under 
// // the License.
// //   
// // The Initial Developer of the Original Code is Robert Smyth.
// // Portions created by Robert Smyth are Copyright (C) 2008-2013.
// //   
// // All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using NDependencyInjection.interfaces;
using NoeticTools.NWorkflow.DotNetInterop;
using NoeticTools.NWorkflow.Exceptions;
using NoeticTools.NWorkflow.ProgressMonitoring;
using NoeticTools.NWorkflow.Scopes;


namespace NoeticTools.NWorkflow.Steps
{
    public partial class Step<TResult, TStepResult> : IStepRules<TStepResult>, IStepRulesScope<TResult, TStepResult>
    {
        private readonly ActionsList OnEntryActions = new ActionsList();
        private readonly ActionsList OnLeavingActions = new ActionsList();
        private readonly ActionsList OnProgressActions = new ActionsList();
        private readonly ActionsList OnTimeoutActions = new ActionsList();
        private readonly List<Action<object>> _onAnyEventActions = new List<Action<object>>();
        private readonly ActionsDictionary<object> _onEventActions = new ActionsDictionary<object>();
        private readonly ActionsList _onReturnActions = new ActionsList();
        protected readonly ISystemDefinition System;
        private bool _progressAvailable;
        private readonly ActionsList _defaultActions = new ActionsList();
        private readonly ActionsDictionary<TStepResult> _resultActions = new ActionsDictionary<TStepResult>();
        private readonly IWorkflowRules<TResult> _workflowRules;
        private readonly IWorkflowRunner<TResult> _workflowRunner;

        public DurationScope Duration
        {
            get { return new DurationScope(this); }
        }

        public bool ProgressAvailable
        {
            get { return _progressAvailable; }
            set
            {
                _progressAvailable = value;
                if (!_progressAvailable)
                {
                    StepRuleProgress.Total = 0.0;
                }
            }
        }

        void IDisposable.Dispose()
        {
        }

        public string StepRulesName { get; private set; }

        public Action[] GetOnEventActions(object eventType)
        {
            var actions = new List<Action>(_onEventActions[eventType].ToArray());
            foreach (var action in _onAnyEventActions)
            {
                actions.Add(() => action(eventType));
            }
            return actions.ToArray();
        }

        public Action[] GetOnEntryActions()
        {
            return OnEntryActions.Actions;
        }

        public Action[] GetOnLeavingActions()
        {
            return OnLeavingActions.Actions;
        }

        public Action[] GetOnTimeoutActions()
        {
            return OnTimeoutActions.Actions;
        }

        public Action[] GetOnReturnActions()
        {
            return _onReturnActions.Actions;
        }

        public IProgress StepRuleProgress { get; protected set; }

        public void AddOnLeavingAction(Action action)
        {
            OnLeavingActions.Add(action);
        }

        public Action OnProgressAction { get; private set; }

        /// <summary>
        ///     Progress percentage delta used as threshold to fire on progress action.
        /// </summary>
        public double OnProgressPercentage { get; private set; }

        public bool TimedProgress { get; set; }

        public void AddOnReturnAction(Action action)
        {
            _onReturnActions.Add(action);
        }

        public void AddOnEntryAction(Action action)
        {
            OnEntryActions.Add(action);
        }

        public void AddOnTimeoutAction(Action action)
        {
            OnTimeoutActions.Add(action);
        }

        public void AddOnProgressAction(Action action)
        {
            OnProgressActions.Add(action);
        }

        public void AddOnProgressAction(double percentageDelta, Action action)
        {
            if (OnProgressAction != null)
            {
                throw new WorkflowConfigurationException("Only one on progress action may be configured for each step.");
            }
            OnProgressPercentage = percentageDelta;
            OnProgressAction = action;
        }

        public void AddOnEventAction<TEvent>(object eventValue, Action action)
        {
            _onEventActions[eventValue].Add(action);
        }

        public void AddOnAnyEventAction(Action<object> action)
        {
            _onAnyEventActions.Add(action);
        }


        public Action[] GetOnResultActions(TStepResult result)
        {
            var actions = new List<Action>(_resultActions[result]);
            if (actions.Count == 0)
            {
                actions.AddRange(_defaultActions.Actions);
            }
            return actions.ToArray();
        }

        /// <summary>
        ///     On entering the workflow step.
        /// </summary>
        public ActionScope<TResult, TStepResult> OnEntry
        {
            get { return new ActionScope<TResult, TStepResult>(_workflowRules, this, AddOnEntryAction, _workflowRunner, System); }
        }

        /// <summary>
        ///     On leaving the workflow step.
        /// </summary>
        public ActionScope<TResult, TStepResult> OnLeaving
        {
            get { return new ActionScope<TResult, TStepResult>(_workflowRules, this, AddOnLeavingAction, _workflowRunner, System); }
        }

        public ActionScope<TResult, TStepResult> OnTimeout
        {
            get { return new ActionScope<TResult, TStepResult>(_workflowRules, this, AddOnTimeoutAction, _workflowRunner, System); }
        }

        public ActionScope<TResult, TStepResult> OnResult(TStepResult result)
        {
            return new ActionScope<TResult, TStepResult>(_workflowRules, this, x => AddResultAction(result, x), _workflowRunner, System);
        }

        public void ExitWithAnyResult<T>()
            where T : TStepResult, TResult
        {
            if (!typeof (T).IsEnum)
            {
                throw new WorkflowConfigurationException("To use ExitWithAnyResult the step and workflow result types must both be the same enums.");
            }

            foreach (var value in Enum.GetValues(typeof (T)))
            {
                OnResult((TStepResult) value).Exit((TResult) value);
            }
        }

        /// <summary>
        ///     On progress percentage change delta.
        /// </summary>
        public ActionScope<TResult, TStepResult> OnProgress(double percentageDelta)
        {
            return new ActionScope<TResult, TStepResult>(_workflowRules, this, action => AddOnProgressAction(percentageDelta, action), _workflowRunner, System);
        }

        public ActionScope<TResult, TStepResult> OnAnyUnhandledResult
        {
            get { return new ActionScope<TResult, TStepResult>(_workflowRules, this, AddDefaultResultAction, _workflowRunner, System); }
        }

        public ActionScope<TResult, TStepResult> OnEvent<TEvent>(TEvent eventValue)
        {
            return new ActionScope<TResult, TStepResult>(_workflowRules, this, action => AddOnEventAction<TEvent>(eventValue, action), _workflowRunner, System);
        }

        public void AddResultAction(TStepResult result, Action action)
        {
            _resultActions[result].Add(action);
        }

        public void AddDefaultResultAction(Action action)
        {
            _defaultActions.Add(action);
        }
    }
}